package k8gbendpoint

/*
Copyright 2021-2025 The k8gb Contributors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic
*/

import (
	"context"
	"fmt"
	"io"
	"net"
	"testing"

	k8gbv1beta1 "github.com/k8gb-io/k8gb/api/v1beta1"
	"github.com/k8gb-io/k8gb/controllers/mocks"
	"github.com/k8gb-io/k8gb/controllers/resolver"
	"github.com/k8gb-io/k8gb/controllers/utils"
	"github.com/miekg/dns"
	"github.com/rs/zerolog"
	"github.com/stretchr/testify/assert"
	"go.uber.org/mock/gomock"
)

func TestWeight(t *testing.T) {
	// void logger
	logger := zerolog.New(io.Discard).With().Timestamp().Logger()
	// void metrics
	metrics := func(*k8gbv1beta1.Gslb, bool, k8gbv1beta1.HealthStatus, []string) {}

	parentZoneDNSServers := []utils.DNSServer{
		{Host: "10.0.0.1", Port: 53},
	}
	type wrr struct {
		weight  int
		region  string
		targets []string
	}

	var tests = []struct {
		name           string
		mockData       []wrr
		config         *resolver.Config
		gslb           *k8gbv1beta1.Gslb
		expectedLabels map[string]string
	}{
		{
			name: "app eu35-us50-za15 - all clusters",
			config: &resolver.Config{
				ClusterGeoTag: "eu",
				DelegationZones: resolver.DelegationZones{
					{
						LoadBalancedZone: "cloud.example.com",
						ParentZone:       "example.com",
						ClusterNSName:    "gslb-ns-eu-cloud.example.com",
						ExtClusterNSNames: map[string]string{
							"us": "gslb-ns-us-cloud.example.com",
							"za": "gslb-ns-za-cloud.example.com",
						},
					},
				},
				ParentZoneDNSServers: parentZoneDNSServers,
			},
			gslb: &k8gbv1beta1.Gslb{
				Spec: k8gbv1beta1.GslbSpec{
					Strategy: k8gbv1beta1.Strategy{
						Type: resolver.RoundRobinStrategy,
						Weight: map[string]int{
							"eu": 35,
							"us": 50,
							"za": 15,
						},
					},
				},
				Status: k8gbv1beta1.GslbStatus{
					ServiceHealth: map[string]k8gbv1beta1.HealthStatus{
						"app.gslb.cloud.example.com": k8gbv1beta1.Healthy,
					},
					LoadBalancer: k8gbv1beta1.LoadBalancer{
						ExposedIPs: []string{"10.10.0.1", "10.10.0.2"},
					},
				},
			},
			mockData: []wrr{
				{region: "eu", weight: 35, targets: []string{"10.10.0.1", "10.10.0.2"}},
				{region: "us", weight: 50, targets: []string{"10.0.0.1", "10.0.0.2"}},
				{region: "za", weight: 15, targets: []string{"10.22.0.1", "10.22.0.2", "10.22.1.1"}},
			},
			expectedLabels: map[string]string{
				"strategy":       resolver.RoundRobinStrategy,
				"weight-us-0-50": "10.0.0.1",
				"weight-us-1-50": "10.0.0.2",
				"weight-eu-0-35": "10.10.0.1",
				"weight-eu-1-35": "10.10.0.2",
				"weight-za-0-15": "10.22.0.1",
				"weight-za-1-15": "10.22.0.2",
				"weight-za-2-15": "10.22.1.1",
			},
		},

		{
			name: "app eu1-us0-za0 - only current cluster",
			config: &resolver.Config{
				ClusterGeoTag: "eu",
				DelegationZones: resolver.DelegationZones{
					{
						LoadBalancedZone: "cloud.example.com",
						ParentZone:       "example.com",
						ClusterNSName:    "gslb-ns-eu-cloud.example.com",
						ExtClusterNSNames: map[string]string{
							"us": "gslb-ns-us-cloud.example.com",
							"za": "gslb-ns-za-cloud.example.com",
						},
					},
				},
				ParentZoneDNSServers: parentZoneDNSServers,
			},
			gslb: &k8gbv1beta1.Gslb{
				Spec: k8gbv1beta1.GslbSpec{
					Strategy: k8gbv1beta1.Strategy{
						Type: resolver.RoundRobinStrategy,
						Weight: map[string]int{
							"eu": 1,
							"us": 0,
							"za": 0,
						},
					},
				},
				Status: k8gbv1beta1.GslbStatus{
					ServiceHealth: map[string]k8gbv1beta1.HealthStatus{
						"app.gslb.cloud.example.com": k8gbv1beta1.Healthy,
					},
					LoadBalancer: k8gbv1beta1.LoadBalancer{
						ExposedIPs: []string{"10.10.0.1", "10.10.0.2"},
					},
				},
			},
			mockData: []wrr{
				{region: "eu", weight: 35, targets: []string{"10.10.0.1", "10.10.0.2"}},
				{region: "us", weight: 50, targets: []string{"10.0.0.1", "10.0.0.2"}},
				{region: "za", weight: 15, targets: []string{"10.22.0.1", "10.22.0.2", "10.22.1.1"}},
			},
			expectedLabels: map[string]string{
				"strategy":      resolver.RoundRobinStrategy,
				"weight-us-0-0": "10.0.0.1",
				"weight-us-1-0": "10.0.0.2",
				"weight-eu-0-1": "10.10.0.1",
				"weight-eu-1-1": "10.10.0.2",
				"weight-za-0-0": "10.22.0.1",
				"weight-za-1-0": "10.22.0.2",
				"weight-za-2-0": "10.22.1.1",
			},
		},

		{
			name: "app eu0-us1-za0 only external cluster",
			config: &resolver.Config{
				ClusterGeoTag: "eu",
				DelegationZones: resolver.DelegationZones{
					{
						LoadBalancedZone: "cloud.example.com",
						ParentZone:       "example.com",
						ClusterNSName:    "gslb-ns-eu-cloud.example.com",
						ExtClusterNSNames: map[string]string{
							"us": "gslb-ns-us-cloud.example.com",
							"za": "gslb-ns-za-cloud.example.com",
						},
					},
				},
				ParentZoneDNSServers: parentZoneDNSServers,
			},
			gslb: &k8gbv1beta1.Gslb{
				Spec: k8gbv1beta1.GslbSpec{
					Strategy: k8gbv1beta1.Strategy{
						Type: resolver.RoundRobinStrategy,
						Weight: map[string]int{
							"eu": 0,
							"us": 1,
							"za": 0,
						},
					},
				},
				Status: k8gbv1beta1.GslbStatus{
					ServiceHealth: map[string]k8gbv1beta1.HealthStatus{
						"app.gslb.cloud.example.com": k8gbv1beta1.Healthy,
					},
					LoadBalancer: k8gbv1beta1.LoadBalancer{
						ExposedIPs: []string{"10.10.0.1", "10.10.0.2"},
					},
				},
			},
			mockData: []wrr{
				{region: "eu", weight: 35, targets: []string{"10.10.0.1", "10.10.0.2"}},
				{region: "us", weight: 50, targets: []string{"10.0.0.1", "10.0.0.2"}},
				{region: "za", weight: 15, targets: []string{"10.22.0.1", "10.22.0.2", "10.22.1.1"}},
			},
			expectedLabels: map[string]string{
				"strategy":      resolver.RoundRobinStrategy,
				"weight-us-0-1": "10.0.0.1",
				"weight-us-1-1": "10.0.0.2",
				"weight-eu-0-0": "10.10.0.1",
				"weight-eu-1-0": "10.10.0.2",
				"weight-za-0-0": "10.22.0.1",
				"weight-za-1-0": "10.22.0.2",
				"weight-za-2-0": "10.22.1.1",
			},
		},

		{
			name: "empty weights with external targets",
			config: &resolver.Config{
				ClusterGeoTag: "eu",
				DelegationZones: resolver.DelegationZones{
					{
						LoadBalancedZone: "cloud.example.com",
						ParentZone:       "example.com",
						ClusterNSName:    "gslb-ns-eu-cloud.example.com",
						ExtClusterNSNames: map[string]string{
							"us": "gslb-ns-us-cloud.example.com",
							"za": "gslb-ns-za-cloud.example.com",
						},
					},
				},
				ParentZoneDNSServers: parentZoneDNSServers,
			},
			gslb: &k8gbv1beta1.Gslb{
				Spec: k8gbv1beta1.GslbSpec{
					Strategy: k8gbv1beta1.Strategy{
						Type:   resolver.RoundRobinStrategy,
						Weight: map[string]int{},
					},
				},
				Status: k8gbv1beta1.GslbStatus{
					ServiceHealth: map[string]k8gbv1beta1.HealthStatus{
						"app.gslb.cloud.example.com": k8gbv1beta1.Healthy,
					},
					LoadBalancer: k8gbv1beta1.LoadBalancer{
						ExposedIPs: []string{"10.10.0.1", "10.10.0.2"},
					},
				},
			},
			mockData: []wrr{
				{region: "eu", weight: 35, targets: []string{"10.10.0.1", "10.10.0.2"}},
				{region: "us", weight: 50, targets: []string{"10.0.0.1", "10.0.0.2"}},
				{region: "za", weight: 15, targets: []string{"10.22.0.1", "10.22.0.2", "10.22.1.1"}},
			},

			expectedLabels: map[string]string{
				"strategy": resolver.RoundRobinStrategy,
			},
		},

		{
			name: "empty weights without",
			config: &resolver.Config{
				ClusterGeoTag: "eu",
				DelegationZones: resolver.DelegationZones{
					{
						LoadBalancedZone:  "cloud.example.com",
						ParentZone:        "example.com",
						ClusterNSName:     "gslb-ns-eu-cloud.example.com",
						ExtClusterNSNames: map[string]string{},
					},
				},
				ParentZoneDNSServers: parentZoneDNSServers,
			},
			gslb: &k8gbv1beta1.Gslb{
				Spec: k8gbv1beta1.GslbSpec{
					Strategy: k8gbv1beta1.Strategy{
						Type:   resolver.RoundRobinStrategy,
						Weight: map[string]int{},
					},
				},
				Status: k8gbv1beta1.GslbStatus{
					ServiceHealth: map[string]k8gbv1beta1.HealthStatus{
						"app.gslb.cloud.example.com": k8gbv1beta1.Healthy,
					},
					LoadBalancer: k8gbv1beta1.LoadBalancer{
						ExposedIPs: []string{"10.10.0.1", "10.10.0.2"},
					},
				},
			},
			mockData: []wrr{
				{region: "eu", weight: 35, targets: []string{"10.10.0.1", "10.10.0.2"}},
			},

			expectedLabels: map[string]string{
				"strategy": resolver.RoundRobinStrategy,
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			// arrange
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()
			cl := mocks.NewMockClient(ctrl)
			qs := mocks.NewMockDNSQueryService(ctrl)
			for i, d := range test.mockData {
				ips := []dns.RR{}
				nsIP := fmt.Sprintf("172.168.10.%v", i+1)
				addr1 := &dns.A{Hdr: dns.RR_Header{Name: dns.Fqdn(
					fmt.Sprintf("gslb-ns-%s-cloud.example.com", d.region)), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 30}, A: net.ParseIP(nsIP)}
				for _, v := range d.targets {
					record := &dns.A{Hdr: dns.RR_Header{Name: dns.Fqdn("localtargets-app.gslb.cloud.example.com"), Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 30},
						A: net.ParseIP(v)}
					ips = append(ips, record)
				}
				qs.EXPECT().
					Query(fmt.Sprintf("gslb-ns-%s-cloud.example.com", d.region), gomock.Any()).Return(&dns.Msg{Answer: []dns.RR{addr1}}, nil).
					AnyTimes()
				qs.EXPECT().
					Query("localtargets-app.gslb.cloud.example.com", utils.DNSList{utils.DNSServer{Host: nsIP, Port: 53}}).Return(&dns.Msg{Answer: ips}, nil).
					AnyTimes()
			}
			qs.EXPECT().ExtractARecords(gomock.Any()).DoAndReturn(
				func(q *dns.Msg) []string { return utils.NewDNSQueryService().ExtractARecords(q) },
			).AnyTimes()

			// act
			ep := NewApplicationDNSEndpoint(context.TODO(), cl, test.config, test.gslb, &logger, qs, metrics)
			endpoint, err := ep.GetDNSEndpoint()

			// assert
			assert.NoError(t, err)
			assert.Equal(t, test.expectedLabels, map[string]string(endpoint.Spec.Endpoints[1].Labels))

		})
	}
}
